﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace DeployGames.TweenEngine
{
    public class Timeline : BaseTween<Timeline>
    {
        public static TimelinePoolCallback poolCallback = new TimelinePoolCallback();
        public static TimelinePool pool = new TimelinePool(10, poolCallback);
        private enum Modes { SEQUENCE, PARALLEL }

        private List<IBaseTween> children = new List<IBaseTween>(10);
        private Timeline current;
        private Timeline parent;
        private Modes mode;
        private bool isBuilt;

        public static int getPoolSize()
        {
            return pool.size();
        }

        public static void ensurePoolCapacity(int minCapacity)
        {
            pool.ensureCapacity(minCapacity);
        }

        public static Timeline createSequence()
        {
            Timeline tl = pool.get();
            tl.setup(Modes.SEQUENCE);
            return tl;
        }

        public static Timeline createParallel()
        {
            Timeline tl = pool.get();
            tl.setup(Modes.PARALLEL);
            return tl;
        }

        public Timeline()
        {
            reset();
        }

        public override void reset()
        {
            base.reset();
            children.Clear();
            current = parent = null;
            isBuilt = false;
        }

        private void setup(Modes mode)
        {
            this.mode = mode;
            this.current = this;
        }

        public Timeline push(Tween tween)
        {
            if (isBuilt) throw new Exception("You can't push anything to a timeline once it is started");
            current.children.Add(tween);
            return this;
        }

        public Timeline push(Timeline timeline)
        {
            if (isBuilt) throw new Exception("You can't push anything to a timeline once it is started");
            if (timeline.current != timeline) throw new Exception("You forgot to call a few 'end()' statements in your pushed timeline");
            timeline.parent = current;
            current.children.Add(timeline);
            return this;
        }

        public Timeline pushPause(float time)
        {
            if (isBuilt) throw new Exception("You can't push anything to a timeline once it is started");
            current.children.Add(Tween.mark().Delay(time));
            return this;
        }

        public Timeline beginSequence()
        {
            if (isBuilt) throw new Exception("You can't push anything to a timeline once it is started");
            Timeline tl = pool.get();
            tl.parent = current;
            tl.mode = Modes.SEQUENCE;
            current.children.Add(tl);
            current = tl;
            return this;
        }

        public Timeline beginParallel()
        {
            if (isBuilt) throw new Exception("You can't push anything to a timeline once it is started");
            Timeline tl = pool.get();
            tl.parent = current;
            tl.mode = Modes.PARALLEL;
            current.children.Add(tl);
            current = tl;
            return this;
        }

        public Timeline end()
        {
            if (isBuilt) throw new Exception("You can't push anything to a timeline once it is started");
            if (current == this) throw new Exception("Nothing to end...");
            current = current.parent;
            return this;
        }

        public List<IBaseTween> getChildren()
        {
            if (isBuilt) return current.children;
            else return current.children;
        }

        public override Timeline build()
        {
            if (isBuilt) return this;

            duration = 0;

            for (int i = 0; i < children.Count; i++)
            {
                IBaseTween obj = children[i];
                if (obj.getRepeatCount() < 0) throw new Exception("You can't push an object with infinite repetitions in a timeline");
                obj.build();

                switch (mode)
                {
                    case Modes.SEQUENCE:
                        float tDelay = duration;
                        duration += obj.getFullDuration();
                        obj.delay += tDelay;
                        break;

                    case Modes.PARALLEL:
                        duration = Math.Max(duration, obj.getFullDuration());
                        break;
                }
            }

            isBuilt = true;
            return this;
        }

        public override Timeline start()
        {
            base.start();
            for (int i = 0; i < children.Count; i++)
            {
                IBaseTween obj = children[i];
                obj.start();
            }
            return this;
        }

        public override void free()
        {
            for (int i = children.Count - 1; i >= 0; i--)
            {
                IBaseTween obj = children[i];
                obj.free();
                children.Remove(obj);
            }
            pool.free(this);
        }

        protected override void updateOverride(int step, int lastStep, bool isIterationStep, float delta)
        {
            if (!isIterationStep && step > lastStep)
            {
                Debug.Assert(delta >= 0);
                float dt = isReverse(lastStep) ? -delta - 1 : delta + 1;
                for (int i = 0, n = children.Count; i < n; i++) children[i].update(dt);
                return;
            }

            if (!isIterationStep && step < lastStep)
            {
                Debug.Assert(delta <= 0);
                float dt = isReverse(lastStep) ? -delta - 1 : delta + 1;
                for (int i = children.Count - 1; i >= 0; i--) children[i].update(dt);
                return;
            }

            Debug.Assert(isIterationStep);

            if (step > lastStep)
            {
                if (isReverse(step))
                {
                    forceEndValues();
                    for (int i = 0, n = children.Count; i < n; i++) children[i].update(delta);
                }
                else
                {
                    forceStartValues();
                    for (int i = 0, n = children.Count; i < n; i++) children[i].update(delta);
                }

            }
            else if (step < lastStep)
            {
                if (isReverse(step))
                {
                    forceStartValues();
                    for (int i = children.Count - 1; i >= 0; i--) children[i].update(delta);
                }
                else
                {
                    forceEndValues();
                    for (int i = children.Count - 1; i >= 0; i--) children[i].update(delta);
                }
            }
            else
            {
                float dt = isReverse(step) ? -delta : delta;
                if (delta >= 0) for (int i = 0, n = children.Count; i < n; i++) children[i].update(dt);
                else for (int i = children.Count - 1; i >= 0; i--) children[i].update(dt);
            }
        }

        protected override void forceStartValues()
        {
            for (int i = children.Count - 1; i >= 0; i--)
            {
                IBaseTween obj = children[i];
                obj.forceToStart();
            }
        }

        protected override void forceEndValues()
        {
            for (int i = 0, n = children.Count; i < n; i++)
            {
                IBaseTween obj = children[i];
                obj.forceToEnd(duration);
            }
        }

        protected override bool containsTarget(object target)
        {
            for (int i = 0, n = children.Count; i < n; i++)
            {
                IBaseTween obj = children[i];
                if (obj.containsTarget(target)) return true;
            }
            return false;
        }

        protected override bool containsTarget(object target, int tweenType)
        {
            for (int i = 0, n = children.Count; i < n; i++)
            {
                IBaseTween obj = children[i];
                if (obj.containsTarget(target, tweenType)) return true;
            }
            return false;
        }
    }
}
